﻿/****************************************************************************
*                                                                           *
* Copyright (c) Ionescu M. Marius. All rights reserved(2010).               *
*                                                                           *
****************************************************************************/

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Web;

/// <summary>
/// Summary description for ImpersonationHelper
/// </summary>
public static class ImpersonationHelper
{
	// group type enum
	private enum SECURITY_IMPERSONATION_LEVEL : int
	{
		SecurityAnonymous = 0,
		SecurityIdentification = 1,
		SecurityImpersonation = 2,
		SecurityDelegation = 3
	}

	// obtains user token
	[DllImport("advapi32.dll", SetLastError = true)]
	private static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

	// closes open handes returned by LogonUser
	[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
	private extern static bool CloseHandle(IntPtr handle);

	// creates duplicate token handle
	[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
	private extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);

	/// <summary>
	/// Attempts to impersonate a user.  If successful, returns 
	/// a WindowsImpersonationContext of the new users identity.
	/// </summary>
	/// <param name="sUsername">Username you want to impersonate</param>
	/// <param name="sDomain">Logon domain</param>
	/// <param name="sPassword">User's password to logon with</param></param>
	/// <returns></returns>
	public static WindowsImpersonationContext ImpersonateUser(string sUsername, string sDomain, string sPassword)
	{
		// initialize tokens
		IntPtr pExistingTokenHandle = new IntPtr(0);
		IntPtr pDuplicateTokenHandle = new IntPtr(0);
		pExistingTokenHandle = IntPtr.Zero;
		pDuplicateTokenHandle = IntPtr.Zero;

		// if domain name was blank, assume local machine
		if (sDomain == "")
			sDomain = System.Environment.MachineName;

		try
		{
			string sResult = null;

			const int LOGON32_PROVIDER_DEFAULT = 0;

			// create token
			const int LOGON32_LOGON_INTERACTIVE = 2;
			//const int SecurityImpersonation = 2;

			// get handle to token
			bool bImpersonated = LogonUser(sUsername, sDomain, sPassword,
				LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref pExistingTokenHandle);

			// did impersonation fail?
			if (false == bImpersonated)
			{
				int nErrorCode = Marshal.GetLastWin32Error();
				sResult = "LogonUser() failed with error code: " + nErrorCode + "\r\n";

				// show the reason why LogonUser failed
				throw new Exception(sResult);
			}

			// Get identity before impersonation
			sResult += "Before impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n";

			bool bRetVal = DuplicateToken(pExistingTokenHandle, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, ref pDuplicateTokenHandle);

			// did DuplicateToken fail?
			if (false == bRetVal)
			{
				int nErrorCode = Marshal.GetLastWin32Error();
				CloseHandle(pExistingTokenHandle); // close existing handle
				sResult += "DuplicateToken() failed with error code: " + nErrorCode + "\r\n";

				// show the reason why DuplicateToken failed
				return null;
			}
			else
			{
				// create new identity using new primary token
				WindowsIdentity newId = new WindowsIdentity(pDuplicateTokenHandle);
				WindowsImpersonationContext impersonatedUser = newId.Impersonate();

				// check the identity after impersonation
				sResult += "After impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n";

				return impersonatedUser;
			}
		}
		catch (Exception)
		{
			throw;
		}
		finally
		{
			// close handle(s)
			if (pExistingTokenHandle != IntPtr.Zero)
				CloseHandle(pExistingTokenHandle);
			if (pDuplicateTokenHandle != IntPtr.Zero)
				CloseHandle(pDuplicateTokenHandle);
		}
	}

	public static WindowsImpersonationContext StartImpersonate()
	{
		HttpContext context = HttpContext.Current;
		// Get the service provider from the context
		IServiceProvider iServiceProvider = context as IServiceProvider;
		//Get a Type which represents an HttpContext
		Type httpWorkerRequestType = typeof(HttpWorkerRequest);
		// Get the HttpWorkerRequest service from the service provider
		// NOTE:  When trying to get a HttpWorkerRequest type from the HttpContext
		// unmanaged code permission is demanded.
		HttpWorkerRequest httpWorkerRequest = iServiceProvider.GetService(httpWorkerRequestType) as HttpWorkerRequest;
		// Get the token passed by IIS
		IntPtr ptrUserToken = httpWorkerRequest.GetUserToken();
		// Create a WindowsIdentity from the token
		WindowsIdentity winIdentity = new WindowsIdentity(ptrUserToken);
		// Impersonate the user
		WindowsImpersonationContext impContext = winIdentity.Impersonate();

		return impContext;
	}

	public static void StopImpersonate(WindowsImpersonationContext impContext)
	{
		// Stop impersonating
		impContext.Undo();
	}

	public static string GetCurrentUser()
	{
		return WindowsIdentity.GetCurrent().Name;
	}

	public static string GetAnonymousUser()
	{
		return WindowsIdentity.GetAnonymous().Name;
	}
}
